﻿using SoundEngine3.AudioProcessing;
using System;

namespace ZquenceStudio3Host.DspPlugins.Synthesizers.Nodesizer.Engine.Waveforms
{
    [ModuleDisplay("Wave Basic")]
    public class BasicWaveformsModule : BaseModule
    {
        private WaveForm mWave;
        private WaveForm mLastWave;
        private static uint m_w = 12350;
        private static uint m_z = 12350;
        private const double NOISE_TO_FLOAT = 1.0 / short.MaxValue;

        public readonly ModuleOutput OutWaveform;

        public readonly ModuleParameter ParameterWaveform;

        public BasicWaveformsModule(ModuleManager manager)
            : base(manager)
        {
            AddOutput(OutWaveform = new ModuleOutput("Waveform", IOType.Waveform, this));

            // Set default wave form.
            mWave = WaveForm.Saw;
            RenderWaveform(mWave);

            ParameterWaveform = new ModuleParameter(this);
            ParameterWaveform.Label = "Waveform";
            ParameterWaveform.MinValue = (int)WaveForm.Sinus;
            ParameterWaveform.MaxValue = (int)WaveForm.Noise;
            ParameterWaveform.DirectGet += delegate { return (int)mWave; };
            ParameterWaveform.DirectSet += delegate (double value)
            {
                mWave = (WaveForm)value;
                if (mLastWave != mWave)
                {
                    RenderWaveform(mWave);
                }
            };
            AddParameter(ParameterWaveform);
        }

        public override void Process()
        {
            if (mWave == WaveForm.Noise)
            {
                RenderWaveform(WaveForm.Noise);
            }
        }

        private void RenderWaveform(WaveForm wave)
        {
            mLastWave = wave;
            WaveFormBuffer wavformBuffer = OutWaveform.AudioBuffer;
            int length = wavformBuffer.Length;

            for (int i = 0; i < length; i++)
            {
                double sin = 1.0 / (double)ModuleOutput.WAVEFORM_BUFFER_LENGTH * i;

                wavformBuffer[i] = GetWaveForm(sin * Math.PI * 2, wave);
            }
        }

        private static double GetWaveForm(double time, WaveForm waveform)
        {
            switch (waveform)
            {
                case WaveForm.Sinus:
                    return Math.Sin(time);
                case WaveForm.Saw:
                    return (Math.Pow(Math.Abs((time % 4) - 2), 2.5) - 2) * 0.25;
                case WaveForm.Square:
                    return Math.Sin(time) > 0 ? 1 : -1;
                case WaveForm.Triangle:
                    double m = 1;
                    return  m - Math.Abs(time % (2 * m) - m) - (m * 0.5);
                case WaveForm.Noise:
                    {
                        m_z = 36969 * (m_z & 65535) + (m_z >> 16);
                        m_w = 18000 * (m_w & 65535) + (m_w >> 16);
                        return (short)((m_z << 16) + m_w) * NOISE_TO_FLOAT;
                    }
                default:
                    return Math.Min(1.0, Math.Max(-1.0, GetRandomForm(time, (uint)waveform * 26362346)));
            }
        }

        private static double GetRandomForm(double time, uint seed)
        {
            // Random wave form.
            time *= 0.1;

            int alg = (int)seed % 3;
            double seed2 = Math.Tan(seed % Math.PI * 2);
            double seed3 = Math.Tan(seed % Math.PI * 20);
            double seed4 = Math.Tan(seed % Math.PI * 40);

            double A = seed2 * 0.1;
            double B = seed3 * 0.1;
            double C = seed4 * 0.1;
            double a = time + seed % 10 * 0.1;
            double b = time + seed % 100 * 0.01;
            double c = time + seed % 100 * 0.01;
            double x = time + seed % 10000 * 0.001;
            double y = time + seed % 100 * 0.01;
            double z = time + seed % 10 * 0.1;

            return
                A * alg == 0 ? Math.Tan(a * x) : alg == 1 ? Math.Tan(a * x) : Math.Tanh(a * x) +
                B * alg == 0 ? Math.Tan(b * x) : alg == 1 ? Math.Tan(b * x) : Math.Tanh(b * x) +
                C * alg == 0 ? Math.Tanh(c * x) : alg == 1 ? Math.Tan(c * x) : Math.Cos(c * x);
        }
    }
}
